Go defer的使用场景及影响分析 | 您所在的位置:网站首页 › go defer用法 › Go defer的使用场景及影响分析 |
目录
前言1. defer特性1.1. 多个defer语句,按照先进后出的方式执行1.2. 延迟函数的参数在defer声明时就决定了1.3. 延迟函数对主函数的匿名返回值和具名返回值的操作影响?1.3.1 函数是如何返回的1.3.2 例1:主函数拥有匿名返回值的影响(不影响返回值)1.3.3 例2:主函数拥有匿名返回值的影响(不影响)1.3.4 例3:主函数拥有具名返回值的影响(影响返回值)
2. defer数据结构3. defer的创建和执行
前言
关键字 defer 用于注册延迟调用,每次 defer 都会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行即 defer 的调用直到return前才会被执行。 defer的用途: 关闭文件句柄锁资源释放数据库连接释放 1. defer特性 1.1. 多个defer语句,按照先进后出的方式执行这个很好理解:后面的语句会依赖前面的资源,因此如果前面的资源先释放了,后面的语句就没法执行了。 每申请到一个用完需要释放的资源时,立即定义一个defer来释放资源是个很好的习惯。 例: func main() { var test [3]struct{} for i := range test { defer fmt.Println(i) } } 输出: 2 1 0defer 碰上闭包 也就是说函数正常执行,由于闭包用到的变量 i 在执行的时候已经变成2,所以输出全都是2. func main() { var test [3]struct{} for i := range test { defer func(){ fmt.Println(i) }() } } 输出: 2 2 2 1.2. 延迟函数的参数在defer声明时就决定了如下代码所示: func test() { i := 0 defer fmt.Println(i) i++ return }defer语句中的 fmt.Println(i) 参数 i 值在 defer 出现时就已经确定下来,实际上是拷贝了一份,后面对变量 i 的修改不会影响 fmt.Println() 函数的执行,仍然打印 0 1.3. 延迟函数对主函数的匿名返回值和具名返回值的操作影响?主函数可能有返回值,返回值有没有名字没关系,defer所作用的函数,即延迟函数可能会影响到返回值。想知道是如果影响的只需要知道函数是如何返回的就够了 1.3.1 函数是如何返回的没有加入 defer 的执行过程 func deferFuncReturn() (result int) { i := 1 return i }deferFuncReturn()函数的return语句可以拆分为2行 result = i return加入 defer 后的执行过程 func deferFuncReturn() (result int) { i := 1 defer func() { result++ }() return i }因为defer的执行时机是在return之前所以加入defer后的执行过程如下: result = i result++ return所以上面函数实际返回i++值。 关于主函数有不同的返回方式,但返回机制就如上介绍所说,只要把return语句拆开都可以很好的理解,下面分别举例说明。 1.3.2 例1:主函数拥有匿名返回值的影响(不影响返回值)一个主函数拥有一个匿名的返回值,返回时使用字面值,比如返回”1”、”2”、”Hello”这样的值,这种情况下defer语句是无法操作返回值的,如下所示: func test() int { var i int defer func() { i++ }() return 1 }上面的return语句,直接把1写入栈中作为返回值,延迟函数无法操作该返回值,所以就无法影响返回值。 1.3.3 例2:主函数拥有匿名返回值的影响(不影响)一个主函数拥有一个匿名的返回值,返回使用本地或全局变量,这种情况下defer语句可以引用到返回值,但不会改变返回值 func test() int { var i int defer func() { i++ }() return i }函数是匿名返回值的情况,它的执行过程过程如下(例1、例2均如此): 将 i 赋值给返回值(可以理解成Go自动创建了一个返回值retrurnValue,相当于执行retrurnValue = i)然后检查是否有defer,如果有则执行返回刚才创建的返回值(retrurnValue)根据例1、例2可得结论: defer中的修改是对 i 执行的,而不是 returnValue,所以defer返回的依然是 returnValue 1.3.4 例3:主函数拥有具名返回值的影响(影响返回值)主函声明语句中带名字的返回值,会被初始化成一个局部变量,函数内部可以像使用局部变量一样使用该返回值。如果defer语句操作该返回值,可能会改变返回结果。 func test() (i int) { defer func() { i++ }() return 0 }以上代码执行过程: i = 0 i++ return函数真正返回前,在defer中对返回值做了+1操作,所以函数最终返回1。 根据例3可得结论: 由于返回值在方法定义时已经被定义,所以没有创建returnValue 的过程,i 就是 returnValue ,defer对于 i 的修改也会被直接返回 2. defer数据结构Go1.7版本的defer的数据结构在包src/runtime/runtime2.go中 943 行有定义 type _defer struct { siz int32 // 参数和返回值共占多少字节 started bool // 是否已经执行 heap bool openDefer bool sp uintptr // 调用者栈指针 pc uintptr // 返回地址 fn *funcval // 函数地址(注册的函数地址) _panic *_panic link *_defer // 链向前一个defer结构体 fd unsafe.Pointer varp uintptr framepc uintptr }defer 的数据结构跟一般函数类似,也有栈地址、程序计数器、函数地址等等。 它与函数不同的一点是:它含有一个指针,可用于指向另一个 defer,每个goroutine 数据结构中实际上也有一个defer指针,该指针指向一个defer的单链表,每次声明一个 defer 时就将 defer 插入到单链表表头,每次执行 defer 时就从单链表表头取出一个 defer 执行 如下图所示: 3. defer的创建和执行创建 defer 和 执行 defer方法的源代码在 src/runtime/panic.go 中 // 创建 func deferproc(siz int32, fn *funcval) {} func deferreturn() {}deferproc():在声明 defer 处调用,将 defer 函数存入 goroutine 的链表中 deferreturn():在return前调用,将 defer 从 goroutine 链表中取出并执行 即在编译阶段,声明defer处插入了函数deferproc(),在函数return前插入了函数deferreturn() |
CopyRight 2018-2019 实验室设备网 版权所有 |